package com.github.rxyor.spring.web.handler;

import com.github.rxyor.common.core.enums.CoreExCodeEnum;
import com.github.rxyor.common.core.enums.KeyValue;
import com.github.rxyor.common.core.exception.CoreException;
import com.github.rxyor.common.core.exception.IllegalParamException;
import com.github.rxyor.common.core.exception.RpcInvokeException;
import com.github.rxyor.common.support.enums.MysqlErrorCodeEnum;
import com.github.rxyor.common.support.hibernate.validate.HibValidatorHelper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import javax.validation.ConstraintViolationException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.exceptions.PersistenceException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 *<p>
 *
 *</p>
 *
 * @author liuyang
 * @date 2020/4/14 周二 14:10:00
 * @since 1.0.0
 */
public abstract class AbstractExceptionHandler<R> {

    /**
     *<p>
     * 抽象方法，根据code获取特定异常提示信息，有些信息是
     * 不能为用户看到的，所以需要特殊化处理
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:13:19
     * @param code code
     * @return replaced friendly  msg
     */
    public abstract String getFriendlyMsgByCode(Comparable<?> code);

    /**
     *<p>
     * 抽象方法，将wrapper转换为Result
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:32:09
     * @param wrapper wrapper
     * @return R
     */
    public abstract R toR(Wrapper<Throwable> wrapper);

    /**
     *<p>
     * 默认失败枚举
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:40:53
     * @return default fail code msg enum
     */
    protected KeyValue<?> defaultFailEnum() {
        return CoreExCodeEnum.FAIL;
    }

    /**
     *<p>
     * 默认Mysql数据库异常code-msg映射
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:20:17
     * @return [java.util.List<com.github.rxyor.common.core.enums.KeyValue < ?>>]
     */
    protected List<KeyValue<Integer>> defaultMysqlFailKVList() {
        return Lists.newArrayList(MysqlErrorCodeEnum.values());
    }


    /**
     *<p>
     * 将异常转换为R
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:48:43
     * @param e e
     * @return result
     */
    protected R process(Throwable e) {
        Comparable<?> code = defaultFailEnum().getCode();
        String msg = defaultFailEnum().getDesc();

        if (e != null) {
            if (e instanceof CoreException) {
                code = ((CoreException) e).getCode();
                msg = ((CoreException) e).getMsg();
            }
        }

        Wrapper<Throwable> wrapper = wrapper(code, msg, e);
        return toR(wrapper);
    }


    /**
     *<p>
     * 将异常转换为R
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:41:53
     * @param code code
     * @param msg msg
     * @param e e
     * @return R
     */
    protected R process(Comparable<?> code, String msg, Throwable e) {
        if (code == null) {
            code = CoreExCodeEnum.FAIL.getCode();
        }
        if (StringUtils.isBlank(msg)) {
            msg = CoreExCodeEnum.FAIL.getDesc();
        }

        Wrapper<Throwable> wrapper = wrapper(code, msg, e);
        return toR(wrapper);
    }

    /**
     *<p>
     * 处理Mysql异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:32:31
     * @param e e
     * @return Result
     */
    protected R processMysqlException(Exception e) {
        Integer code = CoreExCodeEnum.MYSQL_FAIL.getCode();
        String msg = CoreExCodeEnum.MYSQL_FAIL.getDesc();

        Integer vendor = this.loopFindVendorErrorCode(e);
        if (vendor != null && this.defaultMysqlFailKVList() != null) {
            for (KeyValue<Integer> kv : this.defaultMysqlFailKVList()) {
                if (kv != null
                    && vendor.equals(kv.getCode())
                    && StringUtils.isNotBlank(kv.getDesc())) {
                    msg = kv.getDesc();
                }
            }
        }

        Wrapper<Throwable> wrapper = wrapper(code, msg, e);
        return toR(wrapper);
    }

    /**
     *<p>
     *  循环遍历异常堆栈，找出SQLException中对应mysql执行异常码
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:13:10
     * @param e e
     * @return [java.lang.Integer]
     */
    private Integer loopFindVendorErrorCode(Throwable e) {
        int deep = 100;
        while (e != null && deep-- > 0) {
            if (e instanceof SQLException) {
                break;
            }
            e = e.getCause();
        }
        if (e instanceof SQLException) {
            SQLException sqlException = (SQLException) e;
            return sqlException.getErrorCode();
        }
        return null;
    }

    /**
     *<p>
     * wrapper exception
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:30:41
     * @param code code
     * @param msg msg
     * @param e e
     * @return Wrapper
     */
    private Wrapper<Throwable> wrapper(Comparable<?> code, String msg, Throwable e) {
        String friendlyMsg = this.getFriendlyMsgByCode(code);
        Wrapper<Throwable> wrapper;
        if (StringUtils.isNotBlank(friendlyMsg)) {
            wrapper = new Wrapper<>(code, friendlyMsg, e);
        } else {
            wrapper = new Wrapper<>(code, msg, e);
        }
        return wrapper;
    }

    /**
     *<p>
     * Throwable
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:51:12
     * @param e e
     * @return R
     */
    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public R throwable(Throwable e) {
        return process(e);
    }

    /**
     *<p>
     * Exception
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:59:14
     * @param e e
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R exception(Exception e) {
        return process(e);
    }

    /**
     *<p>
     * CoreException: Carp Core Exception
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:00:09
     * @param e e
     * @return R
     */
    @ExceptionHandler(CoreException.class)
    @ResponseBody
    public R coreException(CoreException e) {
        return process(e);
    }

    /**
     *<p>
     * IllegalArgumentException
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:02:22
     * @param e e
     * @return R
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public R illegalArgumentException(IllegalArgumentException e) {
        return process(new IllegalParamException(
            CoreExCodeEnum.ILLEGAL_PARAM.getCode(), e.getMessage(), e));
    }

    /**
     *<p>
     * HttpMessageConversionException: Spring Mvc参数转换异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:02:57
     * @param e e
     * @return R
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    @ResponseBody
    public R httpMessageConversionException(HttpMessageConversionException e) {
        String msg = "参数缺失或参数格式错误";
        return illegalArgumentException(new IllegalArgumentException(msg, e));
    }

    /**
     *<p>
     * ConstraintViolationException: Hibernate校验异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:06:23
     * @param e e
     * @return R
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public R constraintViolationException(ConstraintViolationException e) {
        String msg = Joiner.on(";").join(HibValidatorHelper.parseException(e));
        return illegalArgumentException(new IllegalArgumentException(msg, e));
    }

    /**
     *<p>
     * DataIntegrityViolationException: Spring数据库异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:36:02
     * @param e e
     * @return R
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseBody
    public R dataIntegrityViolationException(DataIntegrityViolationException e) {
        return processMysqlException(e);
    }

    /**
     *<p>
     * PersistenceException: Mybatis数据库异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:36:36
     * @param e e
     * @return R
     */
    @ExceptionHandler(PersistenceException.class)
    @ResponseBody
    public R persistenceException(PersistenceException e) {
        return processMysqlException(e);
    }

    /**
     *<p>
     * 远程调用异常
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 15:42:26
     * @param e e
     * @return R
     */
    @ExceptionHandler(RpcInvokeException.class)
    @ResponseBody
    public R rpcInvokeException(RpcInvokeException e) {
        Integer code = Optional.ofNullable(e).map(CoreException::getCode)
            .orElse(CoreExCodeEnum.RPC_FAIL.getCode());
        String msg = CoreExCodeEnum.RPC_FAIL.getDesc();
        return this.process(code, msg, e);
    }

    /**
     *<p>
     * 异常码、异常信息、异常包装类
     *</p>
     *
     * @author liuyang
     * @date 2020-04-14 周二 14:27:21
     * @since 1.0.0
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Wrapper<T extends Throwable> {

        private Comparable<?> code;
        private String msg;
        private T e;
    }
}
